package simpledb.query;

import java.util.Collection;

import simpledb.parse.QueryData;
import simpledb.planner.OptQueryPlanner;
import simpledb.planner.QueryPlanner;
import simpledb.record.Schema;
import simpledb.tx.Transaction;

/**
 * An expression consisting of a query, to be used on the 
 *  right-hand side of an predicate.
 * @author Kenneth J. Loomis
 * @version December 9, 2011
 */

public class QueryExpression implements Expression {
   private QueryData qry;
   String firstField;
   private Boolean evaluated;
   private Constant val;
   private Transaction thisTx;
   
  /**
   * Executes the query so that the result can be used
   * 	in the predicate.
   */
   private void evaluateValue ()
   {
	   Plan p;
	   Scan s;
	   Schema sch;
	   QueryPlanner  oplanner = new OptQueryPlanner();
	   p = oplanner.createPlan( qry, thisTx );
	   s = p.open();
	   sch = p.schema();
	   s.beforeFirst();
	   s.next();
	   // The field should be in the results, if not throw exception
	   if ( !sch.hasField( firstField ) ) throw new ClassCastException();
	   val = s.getVal( firstField );
	   evaluated = true;
   }
   
   /**
    * Creates a new expression by wrapping a constant.
    * @param c the constant
    */
   public QueryExpression(QueryData q, Transaction tx) {
      qry = q;
      evaluated = false;
      thisTx = tx;
	  Collection<String> f = qry.fields();
	  // There should only be one field here, if there is more
	  // 	than one field throw an exception. Also there should
	  // 	be only one record, but that fact is ignored and only
	  //	the first record found is being used.
      if ( f.isEmpty() || f.size() >= 2 ) throw new ClassCastException();
      firstField = f.iterator().next();
      evaluateValue ();
      return;
   }
   
   /**
    * Returns true.
    * @see simpledb.query.Expression#isConstant()
    */
   public boolean isConstant() {
      return true;
   }
   
   /**
    * Returns false.
    * @see simpledb.query.Expression#isFieldName()
    */
   public boolean isFieldName() {
      return false;
   }
   
   /**
    * Unwraps the constant and returns it.
    * @see simpledb.query.Expression#asConstant()
    */
   public Constant asConstant() {
	   if ( !evaluated )
	   {
		  evaluateValue();
	   }
	   return val;
   }
   
   /**
    * This method should never be called.
    * Throws a ClassCastException.
    * @see simpledb.query.Expression#asFieldName()
    */
   public String asFieldName() {
      throw new ClassCastException();
   }
   
   /**
    * Returns the constant, regardless of the scan.
    * If the underlying plan that returns the constant has
    * not yet been executed to that for the first access of the
    * value. This sub-query should always result in only one 
    * value being returned, for that reason only the first value is
    * returned from the scan.
    * @see simpledb.query.Expression#evaluate(simpledb.query.Scan)
    */
   public Constant evaluate(Scan s2) {
	   // Scan s2, the scan that using the results from the 
	   // 	sub-query, is not used in this function.
	   if ( !evaluated )
	   {
		  evaluateValue();
	   }
	   return val;
   }
   
   /**
    * Returns true, because a constant applies to any schema.
    * @see simpledb.query.Expression#appliesTo(simpledb.record.Schema)
    */
   public boolean appliesTo(Schema sch) {
      return true;
   }
   
   public String toString() {
      return qry.toString();
   }
   

}
